import { loadWasmBinary } from './opus_helper';
import { WL_PromiseCallback } from 'sdk/store/weila_datas';
import { buildOpusEventInd, buildOpusEventReq, OPUS_EVENT } from './opus_event.js';

class WeilaOpusEncoder {
    static instance: any;
    private opusWorker: Worker;
    private inited: boolean;
    private waitingReqList: Map<number, WL_PromiseCallback>;
    private codedDataRecvCallback: ((data: Uint8Array) => void) | null;

    static getInstance() {
        if (!WeilaOpusEncoder.instance) {
            WeilaOpusEncoder.instance = new WeilaOpusEncoder();
            WeilaOpusEncoder.instance.init();
        }
        return WeilaOpusEncoder.instance;
    }


    constructor() {
        const url = new URL('./opus_worker.js', import.meta.url);
        console.log(url.href);
        this.opusWorker = new Worker(new URL('sdk/audio/opus/opus_worker.js', import.meta.url), {
            type: 'module'});

        this.opusWorker.onmessage = this.onMessage.bind(this);
        this.waitingReqList = new Map();
        this.inited = false;
        this.codedDataRecvCallback = null;    
    }

    private async init() {
        return new Promise(async (resolve, reject) => {
            if (this.inited) {
                return;
            }
            
            try {
                await loadWasmBinary();
                const wasmDataBased64 = localStorage.getItem("WASMB");
                if (!wasmDataBased64) {
                    reject(new Error("wasm data not found"));
                }
    
                const wasmData = Uint8Array.from(atob(wasmDataBased64!), c => c.charCodeAt(0));
                const req = buildOpusEventReq(OPUS_EVENT.OPUS_INIT_WORKER_EVT, {wasmData});
                this.waitingReqList.set(OPUS_EVENT.OPUS_INIT_WORKER_EVT, {
                    resolve: resolve,
                    reject: reject
                });
                this.opusWorker.postMessage(req);
            }catch (e) {
                this.inited = false;
                reject(e);
            }            
        })
    }

    public async startEncoder(sampleRate: number, bitRate: number) {
        if (!this.inited) {
            try {
                await this.init();
            }catch (e) {
                return Promise.reject(e);
            }
        }

        return new Promise(async (resolve, reject) => {
            const req = buildOpusEventReq(OPUS_EVENT.OPUS_START_ENCODE_EVT, {sampleRate, bitRate});
            this.waitingReqList.set(OPUS_EVENT.OPUS_START_ENCODE_EVT, {resolve, reject});
            this.opusWorker.postMessage(req);                  
        })
    }

    public encoderData(data: Int16Array) {
        const req = buildOpusEventInd(OPUS_EVENT.OPUS_ENCODE_DATA_EVT, {data});
        this.opusWorker.postMessage(req);
    }

    public async stopEncoder() {
        if (!this.inited) {
            try {
                await this.init();
            }catch (e) {
                return Promise.reject(e);
            }
        }

        return new Promise(async (resolve, reject) => {
            const req = buildOpusEventReq(OPUS_EVENT.OPUS_STOP_ENCODE_EVT);
            this.waitingReqList.set(OPUS_EVENT.OPUS_STOP_ENCODE_EVT, {resolve, reject});
            this.opusWorker.postMessage(req);
        })
    }

    public setCodedDataRecvCallback(callback: (data: Uint8Array) => void) {
        this.codedDataRecvCallback = callback;
    }


    private onMessage(event: MessageEvent) {
        const eventId = event.data.event & 0xFF00;
        const data = event.data.data;
        const promiseCallback = this.waitingReqList.get(eventId);
        switch (eventId) {
            case OPUS_EVENT.OPUS_INIT_WORKER_EVT: {
                console.log("OPUS_INIT_WORKER_EVT");
            }
            break;

            case OPUS_EVENT.OPUS_START_ENCODE_EVT: {
                console.log("OPUS_START_ENCODE_EVT");
            }
            break;

            case OPUS_EVENT.OPUS_STOP_ENCODE_EVT: {
                console.log("OPUS_STOP_ENCODE_EVT");
            }
            break;

            case OPUS_EVENT.OPUS_ENCODE_DATA_EVT: {
                if (this.codedDataRecvCallback) {
                    this.codedDataRecvCallback(data);
                }
            }
            break;
        }

        if (promiseCallback) {
            this.waitingReqList.delete(eventId);
            if (data.errCode == 0) {
                if (promiseCallback.resolve) {
                    promiseCallback.resolve(true)
                }
            }else {
                if (promiseCallback.reject) {
                    promiseCallback.reject(data.errMsg);
                }
            }
        }

    }
}

export {WeilaOpusEncoder}